home *** CD-ROM | disk | FTP | other *** search
/ BBS in a Box 11 / AMUG BBS in a Box Volume XI (April 1994) (MacWizards).iso / Files / Util / After Dark / AD Programming Examples.sit / AD Programming Examples / Programming Examples / Bouncing Ball in C / Bouncing Ball.c next >
Encoding:
C/C++ Source or Header  |  1992-10-12  |  16.1 KB  |  497 lines  |  [TEXT/KAHL]

  1. /*
  2.  *    Bouncing Ball.c
  3.  *    
  4.  *    Example After Dark graphics module implementation.
  5.  *    
  6.  *    •    Uses features of After Dark™, slider and button controls control speed, size and color.
  7.  *    •    Uses classic quickdraw to draw things in black & white.
  8.  *    •    Uses color quickdraw in minimal way to draw a color bouncing ball.
  9.  *    •    Shows how to use sampled, one-shot, sound.
  10.  *    
  11.  *    THINK C version
  12.  *
  13.  *    by Patrick C. Beard and Bruce Burkhalter.
  14.  *    
  15.  *    ©1989, 90, 91 Berkeley Systems, Inc.
  16.  */
  17.  
  18. /*
  19.  
  20. If Think C 4.x is present include the first set of files.  Otherwise
  21. include files for Think C 5.x and MPW 3.x.  In Think C 4.x THINK_C = 1.
  22. In Think C 5.x THINK_C = 5.
  23.  
  24. */
  25.  
  26. #if THINK_C == 1
  27. #include <QuickDraw.h>
  28. #include <MemoryMgr.h>
  29. #include <OSUtil.h>
  30. #include <Color.h>
  31. #else
  32. #include <QuickDraw.h>
  33. #include <Memory.h>
  34. #include <Resources.h>
  35. #include <OSUtils.h>
  36. #include <ToolUtils.h>
  37. #include <FixMath.h>
  38. #include <Picker.h>
  39. #endif
  40.  
  41.  
  42. #include "GraphicsModule_Types.h"
  43. #include "Sounds.h"
  44.  
  45. #define COLOR_BUTTON_MESSAGE    8        /* message that brings up color picker dialog */
  46. #define BALLCOLOR_RGBV            128        /* resource id of 'RGBv' resource to color ball with. */
  47. #define TENNIS_SOUND            128        /* resource id of tennis ball 'snd ' */
  48. #define BOUNCE_COUNT            10        /* number of bounces before changing direction. */
  49.  
  50. /* record to hold bouncing ball's variables. */
  51.  
  52. typedef struct BBStorage {
  53.     Rect        ballRect;        /* the rectangle defining the ball. */
  54.     short        ballDiameter;    /* the current diameter of the ball. */
  55.     Fixed        ballAngle;        /* the current angle ball is traveling. */
  56.     Fixed        ballSin;        /* the current sin of angle of ball. */
  57.     Fixed        ballCos;        /* the current cos of angle of ball. */
  58.     short        xdir;            /* current sign of x-component of velocity */
  59.     short        ydir;            /* current sign of y-component of velocity */
  60.     RGBColor    ballColor;        /* the color to draw the ball in. */
  61.     RGBColor    whiteRGB;        /* the color white expressed in RGB colors. */
  62.     RgnHandle    ballRgn;        /* newer way of dealing with the ball */
  63.     RgnHandle    oldRgn;            /* the previous location of the ball. */
  64.     RgnHandle    diffRgn;        /* the difference in positions */
  65.     long        bounceCount;    /* counter to determine when to choose a new bounce. */
  66.     Boolean        soundAvailable;    /* flag that indicates existence of sound. */
  67.     SoundInfo    **soundInfo;    /* storage for sound. */
  68.     Handle        bounceSound;    /* the sound(s) to make when the ball bounces. */
  69. } BBStorage, *BBStoragePtr, **BBStorageHandle;
  70.  
  71. /* for exception handling, a macro. */
  72.  
  73. #define FailNIL(p)    if(!(p)) { CleanUp((BBStorage**)h); *storage = nil; return -1; }
  74.  
  75. static short random(short lower, short upper);
  76. static void CleanUp(BBStorage** storage);
  77. static Fixed RandomAngle(short lower, short upper);
  78.  
  79. /* routines */
  80.  
  81. /*
  82.  *    DoInitialize is the first function called from After Dark™.
  83.  *    The module allocates and initializes its storage.  This storage is maintained
  84.  *    in the parameter "storage" and the function returns "noErr" if there are no problems.
  85.  */
  86.  
  87. OSErr
  88. DoInitialize(Handle *storage, RgnHandle blankRgn, GMParamBlockPtr params)
  89. {
  90.     OSErr result;
  91.     short i;
  92.     Handle h;
  93.     BBStoragePtr ballStorage;
  94.     RGBColor **savedColor, whiteRGB, blackRGB;
  95.     PixPatHandle pp;
  96.     short ballDiameter;
  97.     RgnHandle ballRgn;
  98.     Rect ballRect;
  99.     Rect monitorRect;
  100.     Fixed ballAngle;
  101.     StringPtr errorMessage = (StringPtr)"\pBouncing Ball:  Sorry, there is not enough memory.";
  102.     
  103.     /* since the sound code is in AD we must make sure the version of AD we are */
  104.     /* running under has it.  AD 2.0u and later have the sound code but we just */
  105.     /* need to check the "extensions" bit in SystemConfig.                        */
  106.     
  107.     if (!(ExtensionsAvailable & params->systemConfig))
  108.     {
  109.         BlockMove("\pBouncing Ball:  Sorry, you need After Dark 2.0u or later.", params->errorMessage, 255);
  110.         return ModuleError;
  111.     }
  112.  
  113.     /* in case we have an error, this message will be displayed. */
  114.     BlockMove(errorMessage, params->errorMessage, 1 + errorMessage[0]);
  115.     
  116.     /* allocate handle to my storage */
  117.     h = NewHandleClear(sizeof(BBStorage));
  118.     FailNIL(h);
  119.     
  120.     /* store a reference to our storage where After Dark™ can keep it. */
  121.     *storage = h;    
  122.     
  123.     /* lock down our storage so we can refer to it by pointer. */
  124.     MoveHHi(h);
  125.     HLock(h);    
  126.     ballStorage = (BBStoragePtr)*h;
  127.  
  128.     /* set up a random initial position of ball. */
  129.     ballStorage->xdir = ballStorage->ydir = 1;    /* sign of velocities is positive */
  130.     ballStorage->ballDiameter = ballDiameter = params->controlValues[1] + 2;    /* 2nd slider value is diameter */
  131.     
  132.     /* place the ball at a random location on the first monitor. */
  133.     monitorRect = params->monitors->monitorList[0].bounds;
  134.     ballRect.left = random(monitorRect.left, monitorRect.right - ballDiameter);
  135.     ballRect.right = ballRect.left + ballDiameter;
  136.     ballRect.top = random(monitorRect.top, monitorRect.bottom - ballDiameter);
  137.     ballRect.bottom = ballRect.top + ballDiameter;
  138.     
  139.     /* allocate a region that describes position of the ball. */
  140.     ballStorage->ballRect = ballRect;
  141.     ballStorage->ballRgn = ballRgn = NewRgn();
  142.     FailNIL(ballRgn);
  143.     OpenRgn();
  144.     FrameOval(&ballRect);
  145.     CloseRgn(ballRgn);
  146.  
  147.     /* initialize counter that keeps track of number of bounces that have occurred. */
  148.     ballStorage->bounceCount = BOUNCE_COUNT;
  149.         
  150.     /* choose a random direction for the ball to travel. */
  151.     ballAngle = RandomAngle(10, 80);
  152.     ballStorage->ballSin = Frac2Fix(FracSin(ballAngle));
  153.     ballStorage->ballCos = Frac2Fix(FracCos(ballAngle));
  154.     
  155.     /* get all scratch regions that we'll ever need */
  156.     ballStorage->oldRgn = NewRgn();
  157.     FailNIL(ballStorage->oldRgn);
  158.     ballStorage->diffRgn = NewRgn();
  159.     FailNIL(ballStorage->diffRgn);
  160.     
  161.     /* if color quickdraw exists, get color information */
  162.     if(params->colorQDAvail) {
  163.         whiteRGB.red = whiteRGB.green = whiteRGB.blue = 0xffff;
  164.         blackRGB.red = blackRGB.green = blackRGB.blue = 0;
  165.         ballStorage->whiteRGB = whiteRGB;
  166.         /* get the color of the ball from our resource fork */
  167.         savedColor = (RGBColor**)GetResource('RGBv', BALLCOLOR_RGBV);
  168.         if(savedColor != nil) {
  169.             ballStorage->ballColor = **savedColor;
  170.             ReleaseResource((Handle)savedColor);
  171.         } else {
  172.             /* default to white if we can't get our resource. */
  173.             ballStorage->ballColor = whiteRGB;
  174.         }
  175.         /* now, make sure this color doesn't map to black.  if it is, use white. */
  176.         if(Color2Index(&ballStorage->ballColor) == Color2Index(&blackRGB))
  177.             ballStorage->ballColor = whiteRGB;
  178.     }
  179.     
  180.     /* NEW:  SOUND SUPPORT */
  181.     
  182.     /* see if a sound channel is even part of the parameter block. */
  183.     ballStorage->soundAvailable = (params->systemConfig & SoundAvailable) != 0;
  184.     
  185.     if(ballStorage->soundAvailable) {
  186.         /* load the resources for our bouncing sounds. */
  187.         ballStorage->bounceSound = GetResource('snd ', TENNIS_SOUND);
  188.         FailNIL(ballStorage->bounceSound);
  189.         
  190.         /* get ready to use sound. */
  191.         /* to use the sound functions in AD 2.0u we must pass in "params" */
  192.         ballStorage->soundInfo = OpenSound(params);
  193.     }
  194.  
  195.     /* unlock storage handle. */    
  196.     HUnlock(h);
  197.  
  198.     return noErr;
  199. }
  200.  
  201. /* we arrive here if anything has failed. */
  202.  
  203. static void CleanUp(BBStorage** storage)
  204. {
  205.     if(storage) {
  206.         BBStorage *ballStorage;
  207.         
  208.         HLock((Handle)storage);
  209.         ballStorage = *storage;
  210.         if(ballStorage->ballRgn)
  211.             DisposeRgn(ballStorage->ballRgn);
  212.         if(ballStorage->oldRgn)
  213.             DisposeRgn(ballStorage->oldRgn);
  214.         if(ballStorage->diffRgn)
  215.             DisposeRgn(ballStorage->diffRgn);
  216.         if(ballStorage->soundAvailable) {
  217.             if(ballStorage->bounceSound)
  218.                 ReleaseResource(ballStorage->bounceSound);
  219.         }
  220.         DisposHandle((Handle)storage);
  221.     }
  222. }
  223.  
  224. /*
  225.  *    DoBlank is called next.
  226.  *    It performs tasks that are done only once, such as making the screen
  227.  *    go black.
  228.  */
  229.  
  230. OSErr
  231. DoBlank(Handle storage, RgnHandle blankRgn, GMParamBlockPtr params)
  232. {
  233.     FillRgn(blankRgn, params->qdGlobalsCopy->qdBlack);
  234.     return noErr;
  235. }
  236.  
  237. /*
  238.  *    DoDrawFrame does almost all of the work.  It draws the ball as a QuickDraw oval of
  239.  *    equal width and height.  It moves the ball according to the "speed" slider and checks
  240.  *    that it is still on one of the monitors in the monitor list.  It draws the ball
  241.  *    with a diameter equal to that set by the "size" slider.
  242.  */
  243.  
  244. /* some macros to simplify synchronizing to the vertical retrace. */
  245. #define SynchFlag(m) (params->monitors->monitorList[m].synchFlag)
  246. #define SynchVBL(m) synchFlag = &SynchFlag(m); *synchFlag = false; while(!*synchFlag);
  247.  
  248. OSErr
  249. DoDrawFrame(storage, blankRgn, params)
  250. Handle storage;
  251. RgnHandle blankRgn;
  252. GMParamBlockPtr params;
  253. {
  254.     register BBStoragePtr ballStorage;    /* to hold dereferenced storage handle */
  255.     register Boolean *synchFlag;        /* pointer to speed up access to synch */
  256.     RgnHandle ballRgn;                    /* current (new) location of the ball */
  257.     RgnHandle oldRgn, diffRgn;            /* regions for updating position */
  258.     Rect ballRect;                        /* current bounding rect of ball */
  259.     Rect intersection;                    /* for finding which monitor ball is on */
  260.     Fixed speed;                        /* speed of the ball. */
  261.     short xVelocity, yVelocity;            /* calculated x, y components of velocity */
  262.     short ballDiameter;                    /* diameter of the ball set by slider. */
  263.     Rect monitorRect;                    /* rectange of monitor we were in. */
  264.     Rect monitorIntersection;            /* how much the ball touches the monitor. */
  265.     short howManyTouching;                /* count of monitors ball is touching. */
  266.     short i, oldMonitor, newMonitor;    /* which monitors we find ourself on */
  267.     Boolean bouncing = false;            /* if we're actually bouncing off a wall. */
  268.     
  269.     /* lock our storage down so we can dereference it for faster access */
  270.     HLock(storage);
  271.     ballStorage = (BBStoragePtr)*storage;
  272.  
  273.     /* get last position of the ball */
  274.     ballRgn = ballStorage->ballRgn;
  275.     oldRgn = ballStorage->oldRgn;
  276.     diffRgn = ballStorage->diffRgn;
  277.     CopyRgn(ballRgn, oldRgn);            /* save last position of the ball */
  278.     
  279.     /* calculate what the current x & y components of velocity should be. */
  280.     speed = Long2Fix(params->controlValues[0]);    /* first slider is speed. */
  281.     xVelocity = Fix2Long(FixMul(speed, ballStorage->ballCos));
  282.     yVelocity = Fix2Long(FixMul(speed, ballStorage->ballSin));
  283.  
  284.     /* move the ball */
  285.     OffsetRgn(ballRgn, ballStorage->xdir * xVelocity, ballStorage->ydir * yVelocity);
  286.         
  287.     /* set up the diameter of the ball */
  288.     ballDiameter = params->controlValues[1] + 2;    /* second slider is the diameter of the ball in pixels */
  289.     ballRect = (**ballRgn).rgnBBox;
  290.     /* has the diameter changed?  if it has, recompute its region. */
  291.     if(ballDiameter != ballStorage->ballDiameter) {
  292.         ballStorage->ballDiameter = ballDiameter;
  293.         ballRect.bottom = ballRect.top + ballDiameter;
  294.         ballRect.right = ballRect.left + ballDiameter;
  295.         OpenRgn();
  296.         FrameOval(&ballRect);
  297.         CloseRgn(ballRgn);
  298.     }
  299.  
  300.     /* look for the monitor that ball WAS on. */
  301.     oldMonitor = 0, newMonitor = -1;
  302.     howManyTouching = 0;
  303.     monitorIntersection = ballRect;
  304.     for(i = 0; i < params->monitors->monitorCount; i++) {
  305.         if(SectRect(&ballStorage->ballRect, &(params->monitors->monitorList[i].bounds), &intersection)) {
  306.             oldMonitor = i;
  307.             monitorRect = params->monitors->monitorList[i].bounds;
  308.         }
  309.         if(SectRect(&ballRect, &(params->monitors->monitorList[i].bounds), &intersection)) {
  310.             newMonitor = i;
  311.             monitorIntersection = intersection;
  312.             howManyTouching++;
  313.         }
  314.     }
  315.  
  316.     /* now that we've moved the ball, see if it is still visible. */
  317.     /* if not, the ball should bounce off the wall and change direction. */
  318.         
  319.     if(newMonitor == -1 || (howManyTouching < 2 && !EqualRect(&ballRect, &monitorIntersection)) ) {
  320.         short deltaX = 0, deltaY = 0;
  321.         /* we are going to bounce the ball. */
  322.         newMonitor = oldMonitor;                /* right back to the old monitor. */
  323.         bouncing = true;
  324.         /* see what directions we have to change. */
  325.         if(ballRect.right > monitorRect.right) {
  326.             ballStorage->xdir = -1;                /* we're off to the right */
  327.             deltaX = monitorRect.right - ballRect.right;
  328.         }
  329.         if(ballRect.left < monitorRect.left) {
  330.             ballStorage->xdir = 1;                /* we're off to the left */
  331.             deltaX = monitorRect.left - ballRect.left;
  332.         }
  333.         if(ballRect.bottom > monitorRect.bottom) {
  334.             ballStorage->ydir = -1;                /* we're off below. */
  335.             deltaY = monitorRect.bottom - ballRect.bottom;
  336.         }
  337.         if(ballRect.top < monitorRect.top) {
  338.             ballStorage->ydir = 1;                /* we're off above. */
  339.             deltaY = monitorRect.top - ballRect.top;
  340.         }
  341.         /* make the ball appear to hit the wall right at its boundary. */
  342.         OffsetRect(&ballRect, deltaX, deltaY);
  343.         OffsetRgn(ballRgn, deltaX, deltaY);
  344.     }
  345.  
  346.     /* record the new position. */
  347.     ballStorage->ballRect = ballRect;
  348.     
  349.     /* are we bouncing? */
  350.     if(bouncing) {
  351.         if(ballStorage->bounceCount-- == 0) {
  352.             /* time to choose a new angle to move along. */
  353.             /* choose an angle between 10° and 80°. */
  354.             Fixed ballAngle = RandomAngle(10, 80);
  355.             ballStorage->bounceCount = BOUNCE_COUNT;
  356.             ballStorage->ballSin = Frac2Fix(FracSin(ballAngle));
  357.             ballStorage->ballCos = Frac2Fix(FracCos(ballAngle));
  358.         }
  359.         /* play the sound. */
  360.         if(ballStorage->soundAvailable)
  361.             PlaySound(ballStorage->soundInfo, params->sndChannel, ballStorage->bounceSound);
  362.     }
  363.     
  364.     /*
  365.      *    Compute the difference between where we're moving and where we are.
  366.      *    This reduces flicker.  The best approach would use an offscreen bitmap.
  367.      */
  368.  
  369.     DiffRgn(oldRgn, ballRgn, diffRgn);
  370.  
  371.     /* if color is available, set up the color to draw in. */
  372.     if(params->colorQDAvail) {
  373.         SynchVBL(newMonitor);                                /* wait until monitor does retrace. */
  374.         RGBBackColor(&ballStorage->whiteRGB);
  375.         FillRgn(diffRgn, params->qdGlobalsCopy->qdBlack);    /* erase old */
  376.  
  377.         /* if this monitor is in greater than 1 bit/pixel, use the color. */
  378.         if(params->monitors->monitorList[newMonitor].curDepth != 1)
  379.             RGBBackColor(&ballStorage->ballColor);
  380.         FillRgn(ballRgn, params->qdGlobalsCopy->qdWhite);    /* draw new */
  381.     }
  382.     else{
  383.         /* draw the bouncing ball in its new location after erasing it in old location. */
  384.         SynchVBL(newMonitor);                                /* wait until monitor does retrace. */
  385.         FillRgn(diffRgn, params->qdGlobalsCopy->qdBlack);    /* erase old */
  386.         FillRgn(ballRgn, params->qdGlobalsCopy->qdWhite);    /* draw new */
  387.     }
  388.     
  389.     HUnlock(storage);
  390.  
  391.     return noErr;
  392. }
  393.  
  394. /*
  395.  *    DoClose() is called when the user wakes up the screen saver.  The memory is deallocated
  396.  *    and the function returns noErr to tell After Dark all is well.
  397.  */
  398.  
  399. OSErr
  400. DoClose(storage, blankRgn, params)
  401. Handle storage;
  402. RgnHandle blankRgn;
  403. GMParamBlockPtr params;
  404. {
  405.     BBStorage **ballStorage = (BBStorage**)storage;
  406.  
  407.     if(ballStorage) 
  408.         CloseSound((**ballStorage).soundInfo, params->sndChannel);
  409.  
  410.     /* deallocate our storage */
  411.     CleanUp(ballStorage);
  412.  
  413.     return noErr;
  414. }
  415.  
  416. /*    
  417.  *    DoSetUp" is called if the user clicks on a button in the Control Panel.  In this case
  418.  *    The only button is to change the color of the ball.  The function calls the "Color
  419.  *    Picker" dialog and then sets the color of the ball to the one chosen.  The color is
  420.  *    then saved in the resource fork of the module.
  421.  */
  422.  
  423. OSErr
  424. DoSetUp(blankRgn, message, params)
  425. RgnHandle blankRgn;
  426. short message;
  427. GMParamBlockPtr params;
  428. {
  429.     Point where;
  430.     Boolean newColorChosen;
  431.     RGBColor **savedColor;
  432.     RGBColor beforeColor, afterColor;
  433.  
  434.     /* here we ask the user to specify the RGB componets of color for the ball. */
  435.     /* use this to set up things that aren't covered easily by params & sliders */
  436.     
  437.     switch(message) {
  438.     case COLOR_BUTTON_MESSAGE:
  439.         WatchCursor();                                                    /* make cursor a watch. */
  440.         where.h = where.v = 0;                                            /* picker will be centered */
  441.         savedColor = (RGBColor**)GetResource('RGBv', BALLCOLOR_RGBV);    /* get the old color */
  442.         if(!savedColor)
  443.             return ResError();
  444.         beforeColor = **savedColor;
  445.         newColorChosen = GetColor(where, "\pBouncing ball color?", &beforeColor, &afterColor);
  446.         if(newColorChosen) {
  447.             /* save the new color to our resource */
  448.             **savedColor = afterColor;
  449.             ChangedResource((Handle)savedColor);
  450.             WriteResource((Handle)savedColor);
  451.         }
  452.         ReleaseResource((Handle)savedColor);
  453.         break;
  454.     default:
  455.         SysBeep(1);
  456.         break;
  457.     }
  458.     return noErr;
  459. }
  460.  
  461. /* utility functions. */
  462.  
  463. static short
  464. random(short lower, short upper)
  465. {
  466.     short middle = (lower+upper)/2;
  467.     short randValue = middle + (Random() % (upper - middle));
  468.     if(randValue < lower)
  469.         return lower;
  470.     if(randValue > upper)
  471.         return upper;
  472.     return randValue;
  473. }
  474.  
  475. /*
  476.  *    Fixed RandomAngle(short lower, short upper);
  477.  *    lower and upper are expressed in degrees, an angle in Fixed point radians between
  478.  *    lower and upper is returned.
  479.  */
  480.  
  481. #define PI_OVER_4    0xC910L                        /* PI/4 expressed in fixed point. */
  482.  
  483. static Fixed
  484. RandomAngle(short lower, short upper)
  485. {
  486.     return FixDiv(FixMul(Long2Fix(random(lower, upper)),PI_OVER_4), Long2Fix(45));
  487. }
  488.  
  489. static
  490. WatchCursor()
  491. {
  492.     CursHandle theCursor = GetCursor(watchCursor);
  493.     if(theCursor) {
  494.         SetCursor(*theCursor);
  495.         ReleaseResource((Handle)theCursor);
  496.     }
  497. }